import { ExternalLink } from 'lucide-react'; import type { Metadata } from 'next'; import Link from 'next/link'; import { permanentRedirect } from 'next/navigation'; import { CompareButton } from '@/components/compare/compare-button'; import { CompareTrayBar } from '@/components/compare/compare-tray-bar'; import { RelationsBlock, SourcesTable, TimelineList } from '@/components/entity/blocks'; import { buildMetadata, loadEntity } from '@/components/entity/load'; import { Methodology } from '@/components/intelligence/bits'; import { ExpandableOfferRow } from '@/components/intelligence/expand-price-row'; import { ViewBeacon } from '@/components/layout/view-beacon'; import { PriceMovers } from '@/components/prices/movers'; import { ProviderStrip } from '@/components/providers/provider-strip'; import { Chip, EntityBadge, StatusBadge } from '@/components/ui/badges'; import { DataTable, EmptyRow, Td, Th } from '@/components/ui/data-table'; import { EntityLink, QualityMark } from '@/components/ui/entity'; import { KeyValue } from '@/components/ui/key-value'; import { SourceCell } from '@/components/ui/provenance'; import { Container, Note, Section } from '@/components/ui/section'; import { EmptyState, Unavailable } from '@/components/ui/unavailable'; import { WatchButton } from '@/components/watchlist/watch-button'; import { api, intel, safe } from '@/lib/api'; import { fmtAgo, fmtDate, fmtInt, fmtTokens, fmtUsdPerM, num } from '@/lib/format'; import { PROSE_KEYS, routes, SITE_NAME, SITE_URL } from '@/lib/site'; import type { Deployment } from '@/lib/types'; export const revalidate = 300; type Params = { params: Promise<{ slug: string }> }; const FEATURE_LABELS: Record = { batch: 'Batch', cached: 'Prompt caching', fine_tuning: 'Fine-tuning', audio: 'Audio', image: 'Image', video: 'Video', web_search: 'Web search', flex: 'Flex tier', long_context: 'Long-context tier', priority: 'Priority tier' }; export async function generateMetadata({ params }: Params): Promise { const { slug } = await params; const d = await safe(api.entityOfType('providers', slug)); if (!d || d.entity_type !== 'provider') return { title: 'Provider', robots: { index: false } }; const m = buildMetadata(d); return { ...m, title: `${d.name} — models served, prices per 1M tokens, price history`, description: `${d.name}${d.organization ? ` (${d.organization.name})` : ''}: every model it currently serves with published input, output, cached and batch prices per 1M tokens, context windows, price distributions, listings, delistings and price change events — each with its source. ${SITE_NAME}.` }; } export default async function ProviderPage({ params }: Params) { const { slug } = await params; const d = await loadEntity('providers', slug); const canonical = routes.entity(d); if (canonical !== `/providers/${encodeURIComponent(slug)}`) permanentRedirect(canonical); const since = Date.now() - 30 * 86400000; const [providers, current, closed, priceEvents] = await Promise.all([safe(intel.providers()), safe(intel.deployments({ provider: d.slug, limit: 200, sort: 'model' })), safe(intel.deployments({ provider: d.slug, current: 0, limit: 200, sort: 'valid_from' })), safe(api.changes({ type: 'PRICE_CHANGED', limit: 200 }))]); const row = providers?.items.find((p) => p.slug === d.slug) ?? null; const deployments: Deployment[] = current?.items ?? []; const added = deployments.filter((x) => new Date(x.valid_from).getTime() >= since).sort((a, b) => b.valid_from.localeCompare(a.valid_from)); const removed = (closed?.items ?? []).filter((x) => x.status === 'delisted' || x.valid_to).sort((a, b) => (b.valid_to ?? '').localeCompare(a.valid_to ?? '')); const events = (priceEvents?.items ?? []).filter((e) => e.meta?.provider_id === d.id || e.meta?.provider === d.name); const orgs = [...new Map(deployments.filter((x) => x.model.organization).map((x) => [x.model.organization!.slug, x.model.organization!])).values()].sort((a, b) => a.name.localeCompare(b.name)); const featureKeys = row?.feature_keys ?? [...new Set(deployments.flatMap((x) => Object.keys(x.features ?? {}).concat(Object.keys(x.prices.native_units ?? {}))))].sort(); const a = d.attributes ?? {}; const specRows = Object.keys(a) .filter((k) => !PROSE_KEYS.has(k)) .map((k) => ({ key: k, raw: a[k] })); const ld = { '@context': 'https://schema.org', '@type': 'Organization', name: d.name, url: typeof a.website === 'string' ? a.website : undefined, description: d.description ?? undefined, parentOrganization: d.organization ? { '@type': 'Organization', name: d.organization.name } : undefined, mainEntityOfPage: `${SITE_URL}${canonical}`, }; return (